AngularJS 1.x 指令作用域初探

总结AngularJS 1.x中 scope 的用法

作用域的作用

  • 每条Angular指令都有其作用域,在指令定义时return的DDO(Directive Definition Object)里设置。

  • scope自身是一个对象:有自己的方法和属性。

Angular会通过scope对象维护所有作用域之间的继承关系。

console.log(scope)

  • 是视图(View)和控制器(Controller)之间的纽带。

创建 controller 时将$scope作为参数传入,可以将 controller 的数据对应展示到 view 中。

1
2
3
<div ng-app="myApp" ng-controller="myCtrl">
<h1>{{carname}}</h1>
</div>
1
2
3
4
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.carname = "Volvo";
});

作用域的取值

代码示例:plunker演示

1
2
3
4
5
6
<div ng-app="test">
<div ng-controller="Ctrl1">
<h2 ng-click="reverseName()">Hey {{name}}, Click me to reverse your name</h2>
<div my-directive class='directive'></div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var app = angular.module("test", []);
app.controller("Ctrl1", function($scope) {
$scope.name = "Harry";
$scope.reverseName = function() {
$scope.name = $scope.name.split('').reverse().join('');
};
});
app.directive("myDirective", function() {
return { //DDO: Directive Defination Object
restrict: "EA",
// scope: false, //使用父作用域,没有自己的作用域。所以父作用域中的name和指令的name之一改变都会影响到对方。
scope: true, //基于父作用域(一般是directive所在的controller,没有的话就是ng-app的rootScope)创建指令自己的作用域,在input内容改变之前,父作用域会影响指令的子作用域(但是input的内容改变后,父作用域就作用不到子作用域了),但子作用域不影响父作用域
// scope: {}, //对象字面量的形式,完全独立于父作用域,从一开始父作用域就不能影响到自己了
link: function(scope){
console.log(scope);
},
template: "<div>Your name is : {{name}}</div>" +
"Change your name : <input type='text' ng-model='name' />"
};
});

1. scope: false (默认值)

指令没有自己的作用域,继承其所在 controller 的作用域,或者rootScope

  • “继承”:在父作用域中修改的变量总是会体现到子作用域中;

  • “没有自己的作用域”:在指令的模板中可以访问和修改父作用域中的变量。

2. scope: true

指令继承了父作用域,但是创建了新的自己的作用域。

  • “继承”:在父作用域中修改的变量会体现到子作用域中(在子作用域的同名变量创建之前);

  • “有自己的作用域”:如果在指令的模板中修改与父作用域同名的变量(仅限于原始数据类型,如 Number / String / Boolean ),则访问这个同名变量时只会访问到子作用域的,访问不到父作用域的(和JS原型继承类似)。因为这样会在子作用域中创建一个新的变量。


几点关于JS原型继承的补充:

主要是在childScope上对基本类型和引用类型的同名变量取值和赋值方面的差别。

  1. 对基本类型和引用类型的变量的取值,都会沿着原型链向上查找;

  2. 对基本类型的赋值:先查找childScope是否有该变量,如果有就更新,如果没有,不会使用原型链查找,而是直接创建该变量然后赋值;后面如果访问这个变量也是访问到childScope中的而不是parentScope中的同名变量。

  3. 对引用类型的赋值:

  • 如果对某对象(或数组、函数)赋值,会在本作用域内查找,有就更新该引用,如果没有也不会使用原型链查找,而是直接新建对象然后赋值,这个和基本类型的赋值一致;

  • 如果对某对象的属性赋值,就会直接查找原型链,找到该对象后直接在原对象上修改,而不会在本地新建。


可能导致的问题:父子作用域之间的通信问题

解决方案:

1)在子作用域中使用$parent.parentScopeProp访问和修改父作用域的数据;

2)更换数据类型:使用Object(引用类型)而不是原始数据类型,来存储需要共享的数据,访问和修改时使用myObject.myPrimitiveplunker演示

3)在父作用域中定义回调函数,子作用域调用可实现修改。

适用于调用不传参或参数不为变量的情况:
jsfiddle演示

引申:AngularJS中的“组件”通信方式(计划放到下一篇KM文章里总结)

【重要】
一些常用的内建指令如:ng-repeat, ng-include, ng-switch, ng-controllerscope值也是true。使用它们会创建新的子作用域,如果不注意上面的方法,可能达不到预期的修改父作用域的数据的效果。

3. scope = {...} 对象字面量

创造完全不依赖于父作用域的新的作用域。

好处:便于组件化,可移植性强。

用法:三种前缀(属性匹配)表示法

代码示例:plunkr演示

先看父作用域:

1
2
3
4
5
6
7
8
9
10
app.controller("MainCtrl", function( $scope ){
$scope.name = "Harry";
$scope.color = "#333333";
$scope.reverseName = function(){
$scope.name = $scope.name.split("").reverse().join("");
};
$scope.randomColor = function(){
$scope.color = '#'+Math.floor(Math.random()*16777215).toString(16);
};
});

子作用域:在DDO中对scope的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
//object iteral - isolated scope. 三种prefix。将scope的property映射到父作用域的同名attribute。也可以在这里更改映射到指定的属性。
scope: {
//text binding 单向绑定。对应的attribute value得用{{}}包裹起来。只能从父作用域传到指令作用域。适用于指令需要从父作用域获得value进行初始化。
name: "@",
//model binding双向绑定。对应的attribute value得是model name
color: "=",
//method binding 对应的attribute value得是父作用域的函数。适用于指令中需要调用父作用域的回调函数
reverse: "&"
},

参考资料

分享
0%